home *** CD-ROM | disk | FTP | other *** search
- /*************************************************************************************************
- *
- *
- * MacZoop - "the framework for the rest of us"
- *
- *
- *
- * ZProgress.cpp -- a progress dialog
- *
- *
- *
- *
- *
- * © 1996, Graham Cox
- *
- *
- *
- *
- *************************************************************************************************/
-
- #include "ZProgress.h"
- #include "MacZoop.h"
- #include "ProjectSettings.h"
-
- #if APPEARANCE_MGR_AWARE
- #include <Appearance.h>
- #endif
-
- /*-----------------------------*** CONSTRUCTOR ***------------------------------------*/
-
- // this is an alternative constructor to set the striped/proportional mode explicitly, or
- // you can use the other constructor passing a negative value to default to striped mode.
-
-
- ZProgress::ZProgress( ZCommander* aBoss,
- const short dialogID,
- const long maxValue,
- const short pType,
- const ProgType aMode )
- : ZDialog( aBoss, dialogID )
- {
- classID = CLASS_ZProgress;
- theType = pType;
- displayMode = -1;
-
- // how many InformProgress() calls between handling an event?
-
- evtProcRatio = 1;
- evtProcCount = evtProcRatio;
- macOS8Control = NULL;
-
- // max should not be less than 1 to avoid potential divide by zero error
-
- max = MAX( 1, ( maxValue & 0x7FFFFFFF ));
-
- value = 0;
- deferTime = 0;
- createTime = TickCount();
- fAbort = FALSE;
- SetRect( &pRect, 0, 0, 0, 0 );
-
- stripesPat = GetPixPat( kStdStripedPattern );
- estTicksToFinish = -1;
-
- SetBeachBallCursor();
- InitZWindow();
-
- // if maxValue is negative, default to indeterminate progress. Otherwise use whatever
- // was passed along in the <aMode> parameter.
-
- if ( maxValue & 0x80000000 )
- SetMode( kIndeterminateProgress );
- else
- SetMode( aMode );
- }
-
-
- ZProgress::ZProgress()
- : ZDialog()
- {
- classID = CLASS_ZProgress;
- }
-
-
- /*------------------------------*** DESTRUCTOR ***------------------------------------*/
-
- ZProgress::~ZProgress()
- {
- Hide();
- // remove the stripes pattern if present
-
- if ( stripesPat )
- DisposePixPat( stripesPat );
- }
-
- /*---------------------------------*** SETUP ***--------------------------------------*/
- /*
-
- overrides ZDialog to set up the button and bar according to the type
- ----------------------------------------------------------------------------------------*/
-
- void ZProgress::SetUp()
- {
- // set up the text of the button to the requested type of dialog
-
- short iType;
- Handle iHand;
- Rect iBox;
-
- if ( theType == kNoButton )
- {
- HideItem( kCancelButton );
-
- // extend the length of the bar in this case so that it is properly
- // centred in the dialog window. (Looks better).
-
- GetDialogItem( macWindow, kBarItem, &iType, &iHand, &iBox );
-
- iBox.right = macWindow->portRect.right - iBox.left;
- SetDialogItem( macWindow, kBarItem, iType, iHand, &iBox );
- }
- else
- {
- // the button is visible, but maybe its text is different.
- // By default it is Cancel, but may need to say "Stop"
-
- if ( theType == kStopType )
- {
- Str31 stopStr;
-
- GetDialogItem(macWindow, kCancelButton, &iType, &iHand, &iBox);
-
- // sanity check- this IS a control, isn't it?
-
- if ( iType & ctrlItem )
- {
- GetIndString( stopStr, 128, kStopStringID );
- SetControlTitle(( ControlHandle ) iHand, stopStr );
- }
- }
- }
-
- // if we have the appearance mgr, make a control for doing the system progress bar
-
- #if APPEARANCE_MGR_AWARE
- if ( gMacInfo.hasAppearanceMgr )
- {
- GetDialogItem( macWindow, kBarItem, &iType, &iHand, &iBox );
- OffsetRect( &iBox, 0, -1 );
- InsetRect( &iBox, -1, 0 );
-
- macOS8Control = NewControl( macWindow,
- &iBox,
- NULL,
- TRUE,
- 0,
- 0,
- iBox.right - iBox.left,
- kControlProgressBarProc,
- (long) this );
- }
- #endif
- // call inherited SetUp in case base class wants to do something
-
- ZDialog::SetUp();
- }
-
- /*---------------------------------*** DRAW ***---------------------------------------*/
- /*
- refresh the dialog
- ----------------------------------------------------------------------------------------*/
-
- void ZProgress::Draw()
- {
- SetRect( &pRect, 0, 0, 0, 0 );
- ZDialog::Draw();
- }
-
-
- /*-----------------------------*** DRAWUSERITEM ***-----------------------------------*/
- /*
-
- Draws the progress bar user item
- ----------------------------------------------------------------------------------------*/
-
- void ZProgress::DrawUserItem( const short item )
- {
- // override to draw the progress bar user-item. This is also called directly when
- // InformProgress is called to update the bar.
-
- if ( item == kBarItem )
- {
- short iType, barLength;
- Handle iHand;
- Rect iBox, barBox;
- PixPatHandle backPP, forePP;
-
- GetDialogItem( macWindow, item, &iType, &iHand, &iBox );
-
- if ( !macOS8Control )
- {
- FrameRect( &iBox );
-
- #ifdef _GREYSCALE_APPEARANCE
-
- FrameGrayRect( &iBox );
-
- #endif
- }
- // if we have the appearance manager, we use the control DEF that implements the
- // current appearance to draw the bar
-
- InsetRect( &iBox, 1, 1 );
-
- // if this is a striped bar, all we do is cycle the stripe and
- // repaint it.
-
- if ( displayMode == kIndeterminateProgress )
- {
- #if APPEARANCE_MGR_AWARE
-
- if ( macOS8Control )
- {
- mode = TRUE;
-
- SetControlData( macOS8Control,
- kControlNoPart,
- kControlProgressBarIndeterminateTag,
- sizeof( Boolean ),
- (Ptr) &mode );
- IdleControls( macWindow );
- }
- else
- {
- #endif
-
- if ( stripesPat )
- {
- CycleStripe();
- FillCRect( &iBox, stripesPat );
- }
- else
- InvertRect( &iBox ); // not a great deal we can do if no resources...
-
- #if APPEARANCE_MGR_AWARE
- }
- #endif
- }
- else
- {
- // compute the size of the barBox, based on max and value
-
- barLength = (short)(((double) value / (double) max) * (long)( iBox.right - iBox.left ));
- barBox = iBox;
- barBox.right = MIN( barBox.left + barLength, iBox.right );
-
- // if this is different to the previously calculated bar, repaint it
-
- if (! EqualRect( &pRect, &barBox ))
- {
- #if APPEARANCE_MGR_AWARE
-
- if ( macOS8Control )
- {
- mode = FALSE;
-
- SetControlData( macOS8Control,
- kControlNoPart,
- kControlProgressBarIndeterminateTag,
- sizeof( Boolean ),
- (Ptr) &mode );
-
- SetControlValue( macOS8Control, barLength );
- }
- else
- {
- #endif
-
- try
- {
- // if this is the first update, pRect will be empty, so
- // simply fill in the background
-
- if ( EmptyRect( &pRect ))
- {
- FailNIL( backPP = GetPixPat( kStdBackPattern ));
- FillCRect( &iBox, backPP );
- DisposePixPat( backPP );
- }
-
- FailNIL( forePP = GetPixPat( kStdBarPattern ));
- FillCRect( &barBox, forePP );
- DisposePixPat( forePP );
- }
- catch ( OSErr err )
- {
- // if the patterns don't exist, paint the bar instead.
-
- PaintRect( &barBox );
-
- // do not propagate exceptions from here
- }
-
- #if APPEARANCE_MGR_AWARE
- }
- #endif
- // update pRect if the window is visible.
-
- if ( IsVisible())
- pRect = barBox;
- }
- }
- }
- else
- ZDialog::DrawUserItem( item );
- }
-
-
- /*-------------------------------*** CLICKITEM ***------------------------------------*/
- /*
- set the abort flag if the user clicked cancel or stop
- ----------------------------------------------------------------------------------------*/
-
- void ZProgress::ClickItem( const short theItem )
- {
- fAbort = ( theItem == kCancelButton );
-
- #if _CANCEL_PROGRESS_THROWS_EXCEPTION
-
- if ( fAbort )
- FailOSErr( userCanceledErr );
-
- #endif
- }
-
-
-
-
- /*-----------------------------*** INFORMPROGRESS ***---------------------------------*/
- /*
- called to keep the progress dialog moving as a lengthy operation proceeds
- ----------------------------------------------------------------------------------------*/
-
- Boolean ZProgress::InformProgress( const long progressSoFar )
- {
- CGrafPtr savePort;
- GDHandle saveDevice;
-
- // save the current port and device in case we were called from
- // some function that had changed it. Note that the caller should be
- // aware that the port, device, etc MAY not be exactly preserved
- // across calls to this, since this processes events, etc.
-
- GetGWorld( &savePort, &saveDevice );
- SetGDevice( GetMainDevice());
-
- // update the value we have got to here
-
- value = progressSoFar;
-
- // if it is time to show the progress dialog, make it visible
-
- if (( TickCount() > ( createTime + deferTime )) && !IsVisible())
- {
- Select();
- PerformUpdate();
- SetBusyArrowCursor();
- }
-
- // let the application handle any pending event, so we get updated, etc. etc
- // any exceptions arising are handled by the caller. Note that in the
- // event of an exception, the device is set to the main device, which
- // is possibly safer, though if the caller knows different, it should
- // try to do the right thing.
-
- // N.B. This is now "geared" down to prevent the progress-monitored function from going
- // too slowly. This processes one event for every <n> calls of this method, where <n> is
- // set by <evtProcRatio>. This defaults to 1, but you can change it to anything you want
- // from 1->32768.
-
- evtProcCount--;
-
- if ( evtProcCount <= 0 )
- {
- gApplication->Process1Event();
- evtProcCount = evtProcRatio;
- }
-
- // redraw the progress bar & time remaining info
-
- Focus();
- DrawUserItem( kBarItem );
- EstimateCompletionTime();
- UpdateTimeToComplete();
-
- // restore what we think is the port and device
-
- SetGWorld( savePort, saveDevice );
-
- return !fAbort; // carry on until this flag is set
- }
-
-
- /*--------------------------------*** SETDELAY ***------------------------------------*/
- /*
- set the initial deferment delay
- ----------------------------------------------------------------------------------------*/
-
- void ZProgress::SetDelay( const short delayTicks )
- {
- deferTime = delayTicks;
- }
-
-
- /*-------------------------------*** SETMESSAGE ***-----------------------------------*/
- /*
- set the message string to the string passed
- ----------------------------------------------------------------------------------------*/
-
- void ZProgress::SetMessage( const Str255& aMsg )
- {
- SetValue( kMessageItem, aMsg );
- }
-
-
- /*-------------------------------*** SETMESSAGE ***-----------------------------------*/
- /*
- set the message to the STR# resource with ID and index passed
- ----------------------------------------------------------------------------------------*/
-
- void ZProgress::SetMessage( const short resID, const short index )
- {
- Str255 aStr;
-
- GetIndString( aStr, resID, index );
- SetMessage( aStr );
- }
-
-
- /*---------------------------------*** SETMODE ***------------------------------------*/
- /*
- switch from stripey to proportional and back
- ----------------------------------------------------------------------------------------*/
-
- void ZProgress::SetMode( ProgType aMode )
- {
- if ( aMode != displayMode )
- {
- displayMode = aMode;
-
- if ( aMode == kIndeterminateProgress )
- {
- HideItem( kTimeRemLabel );
- HideItem( kEstTimeDisplay );
- }
- else
- {
- ShowItem( kTimeRemLabel );
- ShowItem( kEstTimeDisplay );
- }
-
- // force a re-draw of the whole bar
-
- Draw();
- }
- }
-
-
- /*----------------------------------*** FILTER ***------------------------------------*/
- /*
- maps command-period to the cancel/stop button, unless a kNoButton type
- ----------------------------------------------------------------------------------------*/
-
- Boolean ZProgress::Filter( EventRecord* theEvent )
- {
- Boolean fullyHandled = FALSE;
-
- if ( theType != kNoButton )
- {
- if ( theEvent->what == keyDown )
- {
- char theKey = theEvent->message & charCodeMask;
-
- if ((theKey == '.') &&
- ((theEvent->modifiers & cmdKey) == cmdKey))
- {
- FakeClick( kCancelButton );
-
- fullyHandled = TRUE;
- }
- }
- }
-
- return fullyHandled;
- }
-
-
- /*-------------------------------*** CYCLESTRIPE ***----------------------------------*/
- /*
- for stripe bar, this moves the pattern by <loop>, animating it. This is designed to be called
- repeatedly, and only changes the pattern every few (3, in this case) ticks.
- ----------------------------------------------------------------------------------------*/
-
- void ZProgress::CycleStripe()
- {
- if ( stripesPat && IsVisible())
- {
- // see if it is time yet. Notethat this function re-uses the timing
- // data members, but since they are only used before the dialog is visible,
- // this is OK in this particular case.
-
- deferTime = TickCount();
-
- if ( deferTime > ( createTime + 3 ))
- {
- createTime = deferTime;
-
- // modify the pattern. We do this by copying the
- // image data of the pattern to itself in another
- // position, based on the pixmap dimensions.
-
- long patImgSize;
- short rowBytes, loop = 2;
- Ptr tempBuffer;
-
- rowBytes = (*(*stripesPat)->patMap)->rowBytes & 0x3FFF;
- patImgSize = GetHandleSize((*stripesPat)->patData );
-
- // copy the top row of the data into a temporary buffer
- // that we create. This holds one row of image data.
-
- tempBuffer = NewPtr( rowBytes );
-
- do
- {
- BlockMoveData(*(*stripesPat)->patData, tempBuffer, rowBytes);
-
- // copy the rest of the data up one row
-
- BlockMoveData((*(*stripesPat)->patData) + rowBytes, *(*stripesPat)->patData, patImgSize - rowBytes);
-
- // copy the temp row to the bottom of the image
-
- BlockMoveData(tempBuffer, (*(*stripesPat)->patData) + patImgSize - rowBytes, rowBytes);
- }
- while (--loop);
-
- DisposePtr( tempBuffer );
- PixPatChanged( stripesPat );
- }
- }
- }
-
-
-
- /*--------------------------*** ESTIMATECOMPLETIONTIME ***----------------------------*/
- /*
- estimates the time to complete based on the amount completed and the time taken so far.
- This updates <estTicksToFinish>, and does not directly display this information anywhere.
- n.b. if <estTicksToFinish> is -1, the time has not been calculated yet.
- ----------------------------------------------------------------------------------------*/
-
- void ZProgress::EstimateCompletionTime()
- {
- // wait until at least 3% of the bar has been drawn, otherwise the time estimate
- // is wildly exaggerated, due to insufficient sample data.
-
- if ((( value * 100 ) / max ) > 3 )
- {
- unsigned long ticksTakenSoFar = TickCount() - createTime;
- float currentRate = (float) ticksTakenSoFar / (float) value;
- long newEst = (long)( currentRate * (float)( max - value ));
-
- // only increase the current estimate if it's greater by more than 20 seconds,
- // since it may have been just down to a temporary pause...
-
- if ( estTicksToFinish == -1 || ( newEst < estTicksToFinish ) || newEst > ( estTicksToFinish + 1200 ))
- estTicksToFinish = newEst;
- }
- }
-
-
- /*---------------------------*** UPDATETIMETOCOMPLETE ***-----------------------------*/
- /*
- sets up the "Time Remaining:" string. This checks <estTicksToFinish>, and builds a
- suitable string. This is called every time InformProgress is called, but only resets the
- string when it needs to, based on the time resolution indicated.
- ----------------------------------------------------------------------------------------*/
-
- void ZProgress::UpdateTimeToComplete()
- {
- // the default behaviour is similar to the MacOS 8.x Finder. The string will be
- // moderately vague in the interests of user friendliness "About a minute", etc.
-
- if ( estTicksToFinish == -1 )
- return;
-
- static long updateTicks = 0;
- short days, hours, minutes, seconds;
-
- days = estTicksToFinish / 5184000L;
- hours = ( estTicksToFinish - ( days * 5184000L )) / 216000L;
- minutes = ( estTicksToFinish - ( days * 5184000L ) - ( hours * 216000L )) / 3600L;
- seconds = ( estTicksToFinish - ( days * 5184000L ) - ( hours * 216000L ) - ( minutes * 3600L )) / 60L;
-
- // determine if it's time to update yet. We only update if it appears sensible to bother, based on
- // the sort of time we're looking at, or once per minute, whichever is less.
-
- if ( TickCount() >= updateTicks )
- {
- // decide the form of the string, based on the time
-
- Str255 estStr;
- Str15 xx, yy;
- short idx = 0;
-
- xx[0] = 0;
- yy[0] = 0;
-
- if ( days )
- {
- // "more than 24 hours"
-
- idx = 1;
- goto setEstTimeStr;
- }
-
- if ( hours >= 8 )
- {
- // "more than x hours"
-
- NumToString( hours, xx );
- idx = 2;
- goto setEstTimeStr;
- }
-
- if ( estTicksToFinish > 252000L ) // > 1hr, 10 minutes
- {
- // "about x hours, y minutes"
-
- NumToString( hours, xx );
- NumToString( minutes, yy );
- idx = 3;
- goto setEstTimeStr;
- }
-
- if ( estTicksToFinish > 198000L ) // > 55 minutes
- {
- // "about an hour"
- idx = 4;
- goto setEstTimeStr;
- }
-
- if ( estTicksToFinish > 144000L ) // > 40 minutes
- {
- // "less than an hour"
-
- idx = 5;
- goto setEstTimeStr;
- }
-
- if ( estTicksToFinish > 4200L ) // > 1 minute, 10 sec
- {
- // "about x minutes, y seconds
-
- NumToString( minutes, xx );
- NumToString( seconds, yy );
-
- if ( minutes == 1 )
- idx = 11;
- else
- idx = 6;
- goto setEstTimeStr;
- }
-
- if ( estTicksToFinish > 3000L ) // > 50 seconds
- {
- // "about a minute"
-
- idx = 7;
- goto setEstTimeStr;
- }
-
- if ( estTicksToFinish > 1500L ) // > 25 seconds
- {
- // "less than a minute"
-
- idx = 8;
- goto setEstTimeStr;
- }
-
- if ( estTicksToFinish > 120L ) // > 2 seconds
- {
- NumToString( seconds, xx );
- idx = 9;
- goto setEstTimeStr;
- }
-
- idx = 10;
-
- setEstTimeStr:
- GetIndString( estStr, kTimeEstimateStrID, idx );
- ParamText( NULL, NULL, xx, yy );
- SetValue( kEstTimeDisplay, estStr );
-
- updateTicks = TickCount() + 60;
- }
- }
-